﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace Lab_finale_2
{
    // Cette structure sert à prendre en compte le retour ACK ou NACK de l'ordinateur distant.
    public struct AttenteAckNack
    {
        // Contient le dernier code ou position envoyé
        public string trame;
        // Indique si on est en attente d'une réponse
        public bool EnAttente;
        // Indique le nombre de fois qu'on a tent.é d'avoir une réponse pour la dernière trame envoyé
        public int NombreCoup;
        // Pour garder quand la trame a été envoyé. (pour prise en compte avec le timer
        public DateTime quand;
        
    };
    public partial class MainWin : Form
    {
        // On a besoin de ces deux délégués, car la méthode ReceptionPaquet est dans un thread différent et call des contrôle dans la forme présente.
        delegate void SetStartState();
        delegate void SetSymbolePress(object sender, EventArgs e);
        const char ETX = ((char)3);
        // Demande pour débuter une partie
        /// <summary>
        /// Code qui indique à l'ordinateur distant qu'on veut partir une partie
        /// </summary>
        const string STARTGAME = "";
        // Accepte de débuter la partie
        const string FOLLOWGAME = "";
        // explicite
        const string ACK = "";
        // explicite
        /// <summary>
        /// No Acknoledge
        /// </summary>
        const string NACK = "";
        // En tout temps, indique qu'on abandonne la partie
        const string ABANDON = "";
        // Désire ne pas poursuive la partie à la fin
        const string CANCEL = "";
        // Désire poursuivre la partie à la fin
        const string CONTINUE = "";
        /// <summary>
        /// Nombre de coup qui a été joué
        /// </summary>
        private int m_NombreCoup;
        /// <summary>
        /// Indique quel joueur joue un X ou un 0. Vrai joue les X
        /// </summary>
        private bool m_Xou0;
        // indique qu'une partie est abandonné
        private bool m_Abandon;
        /// <summary>
        /// Vrai indique qu'une partie est en cours
        /// </summary>
        private bool m_PartieDemarre;
        /// <summary>
        /// Grille de 3x3 qui contient les références à nos boutons dans la grille. Utile quand la coup est simulé par l'ordinateur distant.
        /// </summary>
        private Button[,] m_TBouton = new Button[3,3];
        /// <summary>
        /// Structure pour aider à prendre en compte les réponse Ack Nack et, dans certains cas, gérer quand on a eu une perte de connection avec l'ordinateur distant.
        /// </summary>
        private AttenteAckNack m_AckNack;
        // Indique que la connection a été établi sur un COM port.
        private bool m_ConnectOnPort;
        private bool m_JoueurXou0;
        private bool m_OrdreJoueur;
        private bool m_Gagnant;

        /// <summary>
        /// Constructeur de la classe sans arguments
        /// </summary>
        public MainWin()
        {
            InitializeComponent();
            m_TBouton[0, 0] = btn1;
            m_TBouton[0, 1] = btn2;
            m_TBouton[0, 2] = btn3;
            m_TBouton[1, 0] = btn4;
            m_TBouton[1, 1] = btn5;
            m_TBouton[1, 2] = btn6;
            m_TBouton[2, 0] = btn7;
            m_TBouton[2, 1] = btn8;
            m_TBouton[2, 2] = btn9;

            // Remise à l'état initial
            InitialState();
            
        }

        /// <summary>
        /// Cet objet démarre à tous les 1 secondes pour vérifier si on est pa en attente d'un ack ou un nack
        /// </summary>
        private void CheckReponse(object sender, EventArgs e)
        {
            DateTime check = DateTime.Now;
            TimeSpan t = check - m_AckNack.quand;
            if (t.TotalSeconds > 10 && m_AckNack.EnAttente)
            {
                lblConnectInterrupt.Visible = true;
                //EnvoiMessage(ABANDON);
                InitialState();
            }
        }

        /// <summary>
        /// Cette fonction remet les contrôles à leur état initial
        /// </summary>
        private void InitialState()
        {
            if (btnCancel.InvokeRequired)
            {
                SetStartState d = new SetStartState(InitialState);
                btnCancel.Invoke(d);
            }
            else
            {
                lblLose.Visible = false;
                lblWin.Visible = false;
                lblTie.Visible = false;
                lblHostCancel.Visible = false;
                btnCancel.Enabled = false;
                btnStart.Visible = true;
                m_Abandon = false;
                m_OrdreJoueur = false;
                m_NombreCoup = 0;
                m_JoueurXou0 = false;
                m_Gagnant = false;
                // tout les boutons de la grille inactif
                foreach (Button b in m_TBouton)
                {
                    b.Enabled = false;
                    b.Text = "";
                }
                m_PartieDemarre = false;
                m_Xou0 = false;
                m_AckNack.EnAttente = false;
                m_AckNack.NombreCoup = 0;
                m_AckNack.trame = "";
                m_AckNack.quand = DateTime.Now;
            }
        }

        /// <summary>
        /// Cette fonction entreprend de démarrer un partie avec un ordinateur distant
        /// </summary>
        private void StartGame(object sender, EventArgs e)
        {
            
            if (m_ConnectOnPort)
            {
                if (lblConnectInterrupt.InvokeRequired)
                {
                    SetSymbolePress d = new SetSymbolePress(StartGame);
                    lblConnectInterrupt.Invoke(d, new object[] { sender, e });
                }
                else
                {
                    InitialState();
                    EnvoiMessage(STARTGAME);
                    m_JoueurXou0 = true;
                    m_OrdreJoueur = true;
                    lblConnectInterrupt.Visible = false;
                }
                //m_Xou0 = true;
            }
        }

        /// <summary>
        /// Change l'état de certains champs et contrôles une fois la partie démarrée
        /// </summary>
        private void StartState()
        {
            m_Xou0 = !m_Xou0;
            if (btnStart.InvokeRequired)
            {
                SetStartState d = new SetStartState(StartState);
                btnStart.Invoke(d);
            }
            else
            {
                btnStart.Visible = false;
                btnCancel.Enabled = true;
            }
            foreach (Button b in m_TBouton)
            {
                if (b.InvokeRequired)
                {
                    SetStartState d = new SetStartState(StartState);
                    b.Invoke(d);
                }
                else
                {
                    b.Enabled = true;
                }
            }
            m_PartieDemarre = true;
        }

        /// <summary>
        /// Sert à indiquer à l'autre joueur qu'on abandonne la partie et pour remettre les chose en position initiale
        /// </summary>
        private void Abandon(object sender, EventArgs e)
        {
            
                lblHostCancel.Visible = true;
                InitialState();
                m_Abandon = true;
                EnvoiMessage(ABANDON);
        }

        /// <summary>
        /// Cette fonction analyse chaque paquet qui arrive pour déterminer si c'est un code ou une position et appel d'autres méthodes conséquement.
        /// </summary>
        private void ReceptionPaquet(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            char temp = ' ';
            string trame = "";
            string codePos = "";
            Thread.Sleep(500);
            while (temp != ETX)
            {
                temp = (char)SPjeux.ReadChar();
                trame += temp.ToString();
            }
            SPjeux.DiscardInBuffer();
            // validation de la trame
            if (TrameTool.CompCheckSumTram(trame) == false)
            {
                EnvoiMessage(NACK);
                return;
            }
            // on vérifie si c'est une position ou un code
            codePos = TrameTool.UndressCode(trame);
            if (codePos.Length == 2) // C'est une position
            {
                // On envoie le code à SymbolePress
                int x = 0, y = 0;
                x = Convert.ToInt32(codePos[0] - '0');
                y = Convert.ToInt32(codePos[1] - '0');
                SymbolePress(m_TBouton[x, y], null);
                //m_JoueurXou0 = !m_JoueurXou0;
                //m_Xou0 = !m_Xou0;
                //m_NombreCoup++;
                EnvoiMessage(ACK);
            }
            else // C'est un code
            {
                switch (codePos)
                {
                    case STARTGAME:
                        if (m_PartieDemarre == false)
                        {
                            InitialState();
                            StartState();
                            EnvoiMessage(ACK);
                            //MessageBox.Show("Start");
                        }
                        else
                        {
                            // ici on peut penser à un bug
                            //MessageBox.Show("Une demande inappropriée pour démarrer une partie a été reçu");
                            InitialState();
                            EnvoiMessage(ABANDON);
                        }
                        break;
                    case ABANDON:
                        RemoteAbort();
                        EnvoiMessage(ACK);
                        break;
                    case ACK:
                        m_AckNack.EnAttente = false;
                        m_AckNack.NombreCoup = 0;
                        m_AckNack.trame = "";
                        if (m_PartieDemarre == false && m_Abandon == false && m_Gagnant == false)
                            StartState();
                        if (m_Abandon)
                            InitialState();
                        break;
                    case NACK:
                        if (m_AckNack.NombreCoup > 1)
                        {
                            MessageBox.Show("Il semble que l'ordinateur distant ne répond plus");
                            EnvoiMessage(ABANDON);
                            InitialState();
                        }
                        else
                        {
                            EnvoiMessage(m_AckNack.trame);
                        }
                        break;
                    default :
                        //MessageBox.Show("Code erroné");
                        // Il n'y a pas vraiment grand chose à faire ici puisque le checksum checker
                        // ramasse les codes erronée. Donc on fait rien si on a un code erroné avec
                        // un bon check sum. Au pire, l,ordi distant va se remettre en InitialState à
                        // force d'attendre pour un ack.
                        break;
                }
            }
        }

        /// <summary>
        /// Cette méthode est appelé à chaque fois que le joueur presse un des boutons de la grille. La méthode ReceptionPaquet peut aussi appeler cette fonction au nom du joueur distant.
        /// </summary>
        private void SymbolePress(object sender, EventArgs e)
        {
            Button b = (Button)sender;
            // on valide si c'est une demande de l'ordi distant et si c'est nous de jouer
            if (m_JoueurXou0 == false)
                if (e != null)
                    return;
            // Ici la référence va se faire à partir de la grille de 3x3. 
            // Si la position est 02, ce sera le bouton m_tBouton[0,2].Enable etc 
            // car si la position est envoyée à partir de la réception de paquet 
            // plutôt que par un bouton, ça ne va plus et on a aucune référence.
            if (b.InvokeRequired)
            {
                SetSymbolePress d = new SetSymbolePress(SymbolePress);
                b.Invoke(d, new object[] { b, e });
            }
            else
            {
                b.Enabled = false;
                if (m_Xou0)
                    b.Text = "X";
                else
                    b.Text = "0";

                if (e != null) // le coup ne provient pas de l'ordi distant, alors on doit le propager
                    EnvoiMessage(b.Tag.ToString());
                m_NombreCoup++;
                m_JoueurXou0 = !m_JoueurXou0;
                m_Xou0 = !m_Xou0;
                CheckSolution();
            }
            
        }

        /// <summary>
        /// Cette méthode vérifie si on a joué tous nos coup et, si c'est le cas, de déterminer si on a un gagnant.
        /// </summary>
        private void CheckSolution()
        {
            
            bool result = false;
            string gagnant = "";

            if (m_TBouton[0, 0].Text != "")
            {
                if ((m_TBouton[0, 0].Text == m_TBouton[0, 1].Text && m_TBouton[0, 0].Text == m_TBouton[0, 2].Text))
                {
                    result = true;
                    gagnant = m_TBouton[0, 0].Text;
                }
            }
            if (m_TBouton[1, 0].Text != "")
            {
                if (m_TBouton[1, 0].Text == m_TBouton[1, 1].Text && m_TBouton[1, 0].Text == m_TBouton[1, 2].Text)
                {
                    result = true;
                    gagnant = m_TBouton[1, 0].Text;
                }
            }
            if (m_TBouton[2, 0].Text != "")
            {
                if (m_TBouton[2, 0].Text == m_TBouton[2, 1].Text && m_TBouton[2, 0].Text == m_TBouton[2, 2].Text)
                {
                    result = true;
                    gagnant = m_TBouton[2, 0].Text;
                }
            }
            if (m_TBouton[0, 0].Text != "")
            {
                if (m_TBouton[0, 0].Text == m_TBouton[1, 0].Text && m_TBouton[0, 0].Text == m_TBouton[2, 0].Text)
                {
                    result = true;
                    gagnant = m_TBouton[0, 0].Text;
                }
            }
            if (m_TBouton[0, 1].Text != "")
            {
                if (m_TBouton[0, 1].Text == m_TBouton[1, 1].Text && m_TBouton[0, 1].Text == m_TBouton[2, 1].Text)
                {
                    result = true;
                    gagnant = m_TBouton[0, 1].Text;
                }
            }
            if (m_TBouton[0, 2].Text != "")
            {
                if (m_TBouton[0, 2].Text == m_TBouton[1, 2].Text && m_TBouton[0, 2].Text == m_TBouton[2, 2].Text)
                {
                    result = true;
                    gagnant = m_TBouton[0, 2].Text;
                }
            }
            if (m_TBouton[0, 0].Text != "")
            {
                if (m_TBouton[0, 0].Text == m_TBouton[1, 1].Text && m_TBouton[0, 0].Text == m_TBouton[2, 2].Text)
                {
                    result = true;
                    gagnant = m_TBouton[0, 0].Text;
                }
            }
            if (m_TBouton[2, 0].Text != "")
            {
                if (m_TBouton[2, 0].Text == m_TBouton[1, 1].Text && m_TBouton[2, 0].Text == m_TBouton[0, 2].Text)
                {
                    result = true;
                    gagnant = m_TBouton[2, 0].Text;
                }
            }
            if(result)
            {
                if (m_OrdreJoueur && gagnant == "X") // Si on est gagnant
                {
                    lblWin.Visible = true;
                    btnStart.Visible = true;
                    m_PartieDemarre = false;
                    m_Gagnant = true;
                }
                else
                    if (m_OrdreJoueur == false && gagnant == "0")
                    {
                        lblWin.Visible = true;
                        btnStart.Visible = true;
                        m_PartieDemarre = false;
                        m_Gagnant = true;
                    }
                    else
                    {
                        lblLose.Visible = true; // Si l'autre est gagnant
                        btnStart.Visible = true;
                        m_PartieDemarre = false;
                        m_Gagnant = true;
                    }
            }
            else   
                if (m_NombreCoup == 9) // si c'est une nulle
                {
                    lblTie.Visible = true;
                    m_PartieDemarre = false;
                    btnStart.Visible = true;
                    m_Gagnant = true;
                }

            
        }


        /// <summary>
        /// Cette méthode envoie un message à un ordi distant. Prend en paramètre une string déjà préparé.
        /// </summary>
        private void EnvoiMessage(string code)
        {
            string trame = TrameTool.BuildTrame(code);
            SPjeux.WriteLine(trame);
            if (code != ACK && code != NACK)
            {
                if (m_AckNack.EnAttente)
                {
                    if (m_AckNack.NombreCoup < 2)
                        m_AckNack.NombreCoup++;
                    else
                    {
                        MessageBox.Show("Il semble que l'ordinateur distant ne répond plus");
                        InitialState();
                    }
                }
                else // Ici on charge la structure AttenteAckNack afin d'évaluer la réponse de l'ordinateur distante pour notre code.
                {
                    m_AckNack.NombreCoup++;
                    m_AckNack.quand = DateTime.Now;
                    m_AckNack.trame = code;
                    m_AckNack.EnAttente = true;
                }
            }
            SPjeux.DiscardOutBuffer();
        }

        /// <summary>
        /// Cette méthode sert à se connecter sur un COM port en utilisant un objet SerialPort
        /// </summary>
        private void ConnectComPort_Click(object sender, EventArgs e)
        {
            int index;
            index = lstbComPort.SelectedIndex;
            if (index > 0)
            {
                try
                {
                    SPjeux.PortName = lstbComPort.Items[index].ToString();
                    SPjeux.Open();
                    m_ConnectOnPort = true;
                    btnConnect.Enabled = false;
                    btnStart.Enabled = true;
                }
                catch (Exception ee)
                {
                    MessageBox.Show(ee.Message);
                }
            }
        }

        /// <summary>
        /// Cette méthode prend en compte quand une demande distante a été faîtes pour abandonner la partie
        /// </summary>
        private void RemoteAbort()
        {
            if (lblHostCancel.InvokeRequired)
            {
                SetStartState d = new SetStartState(RemoteAbort);
                lblHostCancel.Invoke(d);
            }
            else
            {
                lblHostCancel.Visible = true;
                InitialState();
            }
        }

    }
}
